#include <assert.h>

#include <arpa/inet.h>

#include "common_defs.h"
#include "net_context.h"

using namespace usnet;

uint64_t usnet::InetCheckSum(const uint16_t* data, const uint32_t len)
{
    assert(data && len);
    uint64_t csum { 0ull };
    for (uint64_t k = len; k != 0;) {
        csum += static_cast<uint64_t>(data[--k]);
    }
    uint64_t carry { 0 };
    while ((carry = (csum >> 16))) {
        csum &= 0xffff;
        csum += carry;
    }
    return csum;
}

static const char* tcpdump_format {
    "\n  TCP Head:"
    "\n    src port = %-10u, dst port = %-10u;"
    "\n    syn seq  = %-10u, ack seq  = %-10u;"
    "\n    hdr size = %-10u, flags    = %s;"
    "\n    wnd size = %-10u, checksum = %-10x, urg = %-10u;\n"
};

void TCPHeader::Dump(FILE* fp)
{
    assert(fp);
    char flags[33] { };
    int pos { 0 };
    if (tcp_flags_ & RWR_BIT) {
        flags[pos++] = '|';
        flags[pos++] = 'R';
        flags[pos++] = 'W';
        flags[pos++] = 'R';
    }
    if (tcp_flags_ & ECE_BIT) {
        flags[pos++] = '|';
        flags[pos++] = 'E';
        flags[pos++] = 'C';
        flags[pos++] = 'E';
    }
    if (tcp_flags_ & URG_BIT) {
        flags[pos++] = '|';
        flags[pos++] = 'U';
        flags[pos++] = 'R';
        flags[pos++] = 'G';
    }
    if (tcp_flags_ & ACK_BIT) {
        flags[pos++] = '|';
        flags[pos++] = 'A';
        flags[pos++] = 'C';
        flags[pos++] = 'K';
    }
    if (tcp_flags_ & PSH_BIT) {
        flags[pos++] = '|';
        flags[pos++] = 'P';
        flags[pos++] = 'S';
        flags[pos++] = 'H';
    }
    if (tcp_flags_ & RST_BIT) {
        flags[pos++] = '|';
        flags[pos++] = 'R';
        flags[pos++] = 'S';
        flags[pos++] = 'T';
    }
    if (tcp_flags_ & SYN_BIT) {
        flags[pos++] = '|';
        flags[pos++] = 'S';
        flags[pos++] = 'Y';
        flags[pos++] = 'N';
    }
    if (tcp_flags_ & FIN_BIT) {
        flags[pos++] = '|';
        flags[pos++] = 'F';
        flags[pos++] = 'I';
        flags[pos++] = 'N';
    }
    fprintf(
        fp,
        tcpdump_format,
        ntohs(be_lport_),
        ntohs(be_rport_),
        ntohl(be_syn_seq_),
        ntohl(be_ack_seq_),
        static_cast<uint16_t>(hdr_len_ >> 4),
        flags,
        ntohs(be_rwnd_size_),
        ntohs(be_checksum_),
        ntohs(be_urg_ptr_));
}

static const char* ip4dump_format {
    "\n  IPv4 Head:"
    "\n    version = %-4u, hdr size = %u, DS&ECN = %2x;"
    "\n    pkt len = %-4u, pkt id   = %-10u;"
    "\n    ttl     = %-4u, ppt = %s, checksum = %4x;"
    "\n    src address = %-16s, dst address = %-16s;"
};

void IP4Header::Dump(FILE* fp)
{
    assert(fp);
    char ppt[8] { "NONE"};
    char src_addr[16] { };
    char dst_addr[16] { };
    if (protocol_ ==  static_cast<uint8_t>(PPT::TCP)) {
        ppt[0] = 'T';
        ppt[1] = 'C';
        ppt[2] = 'P';
        ppt[3] = '\0';
    }
    if (protocol_ ==  static_cast<uint8_t>(PPT::UDP)) {
        ppt[0] = 'U';
        ppt[1] = 'D';
        ppt[2] = 'P';
        ppt[3] = '\0';
    }
    inet_ntop(AF_INET, &be_laddr_, src_addr, sizeof(src_addr));
    inet_ntop(AF_INET, &be_raddr_, dst_addr, sizeof(dst_addr));
    fprintf(
        fp,
        ip4dump_format,
        static_cast<uint16_t>(ipvhl_ >> 4),
        static_cast<uint16_t>(ipvhl_ & 0x0f),
        static_cast<uint16_t>(tos_),
        ntohs(be_len_), ntohs(be_pid_),
        static_cast<uint16_t>(ttl_), ppt, ntohs(be_checksum_),
        src_addr, dst_addr);
}

static const char* ethdump_format {
    "\nEth Head:"
    "\n  ppt = %s;"
    "\n  src address = %02x:%02x:%02x:%02x:%02x:%02x;"
    "\n  dst address = %02x:%02x:%02x:%02x:%02x:%02x;"
};
void EthHeader::Dump(FILE* fp)
{
    char ppt[8] { "NONE" };
    if (ntohs(be_protocol_) == static_cast<uint16_t>(PPT::IPv4)) {
        ppt[0] = 'I';
        ppt[1] = 'P';
        ppt[2] = 'v';
        ppt[3] = '4';
    }
    if (ntohs(be_protocol_) == static_cast<uint16_t>(PPT::IPv6)) {
        ppt[0] = 'I';
        ppt[1] = 'P';
        ppt[2] = 'v';
        ppt[3] = '6';
    }
    fprintf(
        fp,
        ethdump_format,
        ppt,
        static_cast<uint16_t>(src_ethaddr_[0]),
        static_cast<uint16_t>(src_ethaddr_[1]),
        static_cast<uint16_t>(src_ethaddr_[2]),
        static_cast<uint16_t>(src_ethaddr_[3]),
        static_cast<uint16_t>(src_ethaddr_[4]),
        static_cast<uint16_t>(src_ethaddr_[5]),
        static_cast<uint16_t>(dst_ethaddr_[0]),
        static_cast<uint16_t>(dst_ethaddr_[1]),
        static_cast<uint16_t>(dst_ethaddr_[2]),
        static_cast<uint16_t>(dst_ethaddr_[3]),
        static_cast<uint16_t>(dst_ethaddr_[4]),
        static_cast<uint16_t>(dst_ethaddr_[5]));
    return;
}